package com.rfid.webp2plib;

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.HardwareVideoDecoderFactory;
import org.webrtc.HardwareVideoEncoderFactory;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoCodecInfo;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Created by deanliu on 2019/9/4.
 */

public class WebRtcClient {
    private static final String TAG = "webrtc";
    private PeerConnectionFactory mFactory;
    private Executor mExecutor = Executors.newSingleThreadExecutor();
    private LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<>();
    private EglBase mEglBase;
    private Context mContext;
    private MediaConstraints mMediaConstraints;
    private Map<String, Peer> mPeers;

    private VideoCapturer mVideoCapturer;
    private SurfaceTextureHelper mSurfaceTextureHelper;
    private VideoTrack mVideoTrack; //用于渲染
    private VideoSource mVideoSource;

    private List<PeerListener> mPeerListeners;

    protected WebRtcClient(Context context) {
        init(context);
    }

    private void init(Context context) {
        Log.v(TAG,"ScreenRtcClient INIT");
        mContext = context;
        mPeerListeners = new ArrayList<>();
        mPeers = new HashMap<>();
        PeerConnectionFactory.initialize(PeerConnectionFactory
                .InitializationOptions
                .builder(context)
                .createInitializationOptions());
        mEglBase = EglBase.create();
        VideoEncoderFactory encoderFactory = new DefaultVideoEncoderFactory(
                mEglBase.getEglBaseContext(), false /* enableIntelVp8Encoder */, true);
//        encoderFactory = new HardwareVideoEncoderFactory(mEglBase.getEglBaseContext(),false,true);
        encoderFactory = new SoftwareVideoEncoderFactory();
        VideoDecoderFactory decoderFactory = new DefaultVideoDecoderFactory(mEglBase.getEglBaseContext());
        mFactory = PeerConnectionFactory.builder().setOptions(new PeerConnectionFactory.Options()).setVideoDecoderFactory(new SoftwareVideoDecoderFactory()).setVideoEncoderFactory(encoderFactory).createPeerConnectionFactory();
//        iceServers.add(new PeerConnection.IceServer("stun:119.29.60.16:3478"));
//        iceServers.add(new PeerConnection.IceServer("turn:119.29.60.16:3478?transport=tcp","liuwei","coco5200"));
//        iceServers.add(new PeerConnection.IceServer("turn:119.29.60.16:3478","liuwei","coco5200"));
        iceServers.add(new PeerConnection.IceServer("stun:3.tcp.vip.cpolar.cn:10879"));
        iceServers.add(new PeerConnection.IceServer("turn:3.tcp.vip.cpolar.cn:10879?transport=tcp","liuwei","coco5200"));
        iceServers.add(new PeerConnection.IceServer("stun:stun.ekiga.net"));
        iceServers.add(new PeerConnection.IceServer("stun:stun.ideasip.com"));
        iceServers.add(new PeerConnection.IceServer("stun:stun.schlund.de"));
        iceServers.add(new PeerConnection.IceServer("stun:stun.voiparound.com"));
        iceServers.add(new PeerConnection.IceServer("stun:stun.voipbuster.com"));

        mMediaConstraints = new MediaConstraints();
//        mMediaConstraints.optional.add(new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
        encoderFactory = new HardwareVideoEncoderFactory(mEglBase.getEglBaseContext(),false,true);
        decoderFactory = new HardwareVideoDecoderFactory(mEglBase.getEglBaseContext());
        VideoCodecInfo[] eninfo = encoderFactory.getSupportedCodecs();
        Log.v(TAG,"ENSize = " + eninfo.length);
        for(VideoCodecInfo info:eninfo) {
            Log.v(TAG,"eninfo = " + info.name);
        }
        VideoCodecInfo[] deinfo = decoderFactory.getSupportedCodecs();
        Log.v(TAG,"deSize = " + deinfo.length);
        for(VideoCodecInfo info:deinfo) {
            Log.v(TAG,"deinfo = " + info.name);
        }
    }

    public void release() {
        Log.v(TAG,"release");
        mVideoCapturer = null;
        synchronized (mPeerListeners) {
            mPeerListeners.clear();
        }
        for (Peer peer : mPeers.values()) {
            peer.release();
        }
        mPeers.clear();
        Log.v(TAG,"peers released");
        if(mVideoSource != null) {
            mVideoSource.dispose();
            Log.v(TAG,"mVideoSource released");
        }

        mFactory.dispose();
        Log.v(TAG,"mFactory released");
        if(mSurfaceTextureHelper != null) {
            mSurfaceTextureHelper.dispose();
            Log.v(TAG,"mSurfaceTextureHelper released");
        }
        mEglBase.release();
        Log.v(TAG,"mEglBase released");
    }

    public void registerPeerListener(PeerListener listener) {
        synchronized (mPeerListeners) {
            if (!mPeerListeners.contains(listener)) {
                mPeerListeners.add(listener);
            }
        }
    }

    public void unRegisterPeerListener(PeerListener listener) {
        synchronized (mPeerListeners) {
            mPeerListeners.remove(listener);
        }
    }

    public EglBase getEglBase() {
        return mEglBase;
    }

    public void createOffer(String id) {
        Peer peer = getPeer(id);
        Log.v(TAG,"createOffer id = " + id + ",peers size = " + mPeers.size());
        peer.createOffer(mMediaConstraints);
    }

    public void createAnswer(String id, SessionDescription sdp) {
        Log.v(TAG,"createAnswer ID = " + id);
        Peer peer = getPeer(id);
        peer.pc.setRemoteDescription(peer,sdp);
        peer.createAnswer(mMediaConstraints);
    }

    public void prepareVideoStream(String id) {
        getPeer(id).prpareVideoStream();
    }

    public void receiveAnswer(String id, SessionDescription sdp) {
        Log.v(TAG,"receiveAnswer  id = " + id + ",peers size = " + mPeers.size());
        Peer peer = getPeer(id);
        peer.pc.setRemoteDescription(peer,sdp);
    }

    public void addCandidate(String id, IceCandidate candidate) {
        Peer peer = getPeer(id);
        synchronized (peer) {
            if (peer.queuedIceCandiadate == null) {
                Log.i(TAG, "addCandidate! id = " + id + ",candidate = " + candidate);
                peer.pc.addIceCandidate(candidate);
                Log.i(TAG,"addCandidate OVER");
            } else {
                Log.w(TAG, "not addCandidate!add to queuedIceCandiadate! id = " + id);
                peer.queuedIceCandiadate.add(candidate);
            }
        }
    }

    public void closePeer(final String id) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Peer peer = getPeer(id);
                peer.release();
                mPeers.remove(id);
            }
        });
    }

    public MediaStream getRemoteStream(String id) {
        Peer peer = getPeer(id);
        return peer.remoteStream;
    }

    private Peer getPeer(String from) {
        Peer peer;
        if (!mPeers.containsKey(from)) {
            peer = addPeer(from);
        } else {
            peer = mPeers.get(from);
        }
        return peer;
    }

    private Peer addPeer(String id) {
        Peer peer = new Peer(id);
        mPeers.put(id, peer);
        return peer;
    }

    public boolean sendMsg(String id, byte data[]) {
        Peer peer = mPeers.get(id);
        if(peer == null) {
            return false;
        }
        System.out.println("sendMsg = " + new String(data));
        System.out.println( peer.dc.state());
        Log.v(TAG,"sendMsg = " + new String(data) + "," + peer.dc.state());
        DataChannel.Buffer buffer = new DataChannel.Buffer(
                ByteBuffer.wrap(data),
                true);
        return peer.dc.send(buffer);
    }

    public boolean isComplete(String id) {
        Peer peer = getPeer(id);
        if (peer == null) {
            return false;
        }
        return peer.dc.state() == DataChannel.State.OPEN;
    }

    public class Peer implements SdpObserver, PeerConnection.Observer, DataChannel.Observer{

        PeerConnection pc;
        String id;
        DataChannel dc;

        private MediaStream mediaStream;
        MediaStream remoteStream;
        private boolean isInitiator;
        List<IceCandidate> queuedIceCandiadate;

        public Peer(String id) {
            Log.d(TAG, "new Peer: " + id);
            queuedIceCandiadate = new ArrayList<>();
            this.pc = mFactory.createPeerConnection(
                    iceServers, //ICE服务器列表
                    mMediaConstraints, //MediaConstraints
                    this); //Context
            this.id = id;
            DataChannel.Init init = new DataChannel.Init();
            init.ordered = true;
            dc = pc.createDataChannel("dataChannel", init);
        }


        public void createAnswer(MediaConstraints mediaConstraints) {
            isInitiator = false;
            if(mediaStream != null) {
                pc.addStream(mediaStream);
            }
            pc.createAnswer(this,mediaConstraints);
        }

        public void createOffer(MediaConstraints mediaConstraints) {
            isInitiator = true;
            if(mediaStream != null) {
                pc.addStream(mediaStream);
            }
            pc.createOffer(this,mediaConstraints);
        }

        public void prpareVideoStream() {
            if(mVideoCapturer != null) {
                mVideoTrack = mFactory.createVideoTrack("videotrack", mVideoSource);
                mediaStream = mFactory.createLocalMediaStream("localStream");
                mediaStream.addTrack(mVideoTrack);
            }
        }

        public void release() {
            dc.dispose();
            pc.close();
            pc.dispose();
        }

        @Override
        public void onBufferedAmountChange(long previousAmount) {
            System.out.println("onBufferedAmountChange > " + previousAmount);
        }

        @Override
        public void onStateChange() {
            Log.d(TAG,"onStateChange");
        }

        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            Log.d(TAG, "onDataChannel onMessage : " + buffer);
            ByteBuffer data = buffer.data;
            byte[] bytes = new byte[data.capacity()];
            data.get(bytes);
            synchronized (mPeerListeners) {
                for (PeerListener peerListener : mPeerListeners) {
                    peerListener.onMessage(id, bytes);
                }
            }
        }

        @Override
        public void onSignalingChange(PeerConnection.SignalingState newState) {

        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState newState) {
            Log.i(TAG, "onIceConnectionChange : " + newState.name());
            if (newState == PeerConnection.IceConnectionState.DISCONNECTED) {
                isInitiator = false;
                queuedIceCandiadate = new ArrayList<>();
                synchronized (mPeerListeners) {
                    for (PeerListener peerListener : mPeerListeners) {
                        peerListener.onIceDisconnect(id);
                    }
                }
                closePeer(id);
            } else if (newState == PeerConnection.IceConnectionState.CONNECTED) {
                synchronized (mPeerListeners) {
                    for (PeerListener peerListener : mPeerListeners) {
                        peerListener.onIceConnect(id);
                    }
                }
            } else if(newState == PeerConnection.IceConnectionState.FAILED) {
                isInitiator = false;
                queuedIceCandiadate = new ArrayList<>();
                if(mContext instanceof Activity) {
                    ((Activity)mContext).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(mContext,"connect Failed!!!", Toast.LENGTH_LONG).show();
                        }
                    });
                }
            } else if (newState == PeerConnection.IceConnectionState.COMPLETED) {
                synchronized (mPeerListeners) {
                    for (PeerListener peerListener : mPeerListeners) {
                        peerListener.onCompleted(id);
                    }
                }
            }
        }

        @Override
        public void onIceConnectionReceivingChange(boolean receiving) {
            Log.w(TAG, "onIceConnectionReceivingChange");
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState newState) {
            Log.w(TAG, "onIceGatheringChange newState = " + newState.name());
        }

        @Override
        public void onIceCandidate(IceCandidate candidate) {
            Log.i(TAG, "onIceCandidate : " + candidate.serverUrl);
            try {
                JSONObject payload = new JSONObject();
                payload.put("label", candidate.sdpMLineIndex);
                payload.put("id", candidate.sdpMid);
                payload.put("candidate", candidate.sdp);
                synchronized (mPeerListeners) {
                    for (PeerListener peerListener : mPeerListeners) {
                        peerListener.onIceCandidate(id, "candidate", payload);
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] candidates) {
            Log.d(TAG, "onIceCandidatesRemoved");
        }

        @Override
        public void onAddStream(MediaStream stream) {
            Log.d(TAG, "onAddStream :" + stream.videoTracks.size());
            remoteStream = stream;
            synchronized (mPeerListeners) {
                for (PeerListener peerListener : mPeerListeners) {
                    peerListener.onAddRemoteStream(id, stream);
                }
            }
        }

        @Override
        public void onRemoveStream(MediaStream stream) {
            Log.d(TAG, "onRemoveStream :" + stream.videoTracks.size());
        }

        @Override
        public void onDataChannel(DataChannel dataChannel) {
            Log.d(TAG, "onDataChannel label:" + dataChannel.label());
            System.out.println("onDataChannel");
            dataChannel.registerObserver(this);
        }

        @Override
        public void onRenegotiationNeeded() {

        }

        @Override
        public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) {

        }

        @Override
        public void onCreateSuccess(SessionDescription sdp) {
            Log.d(TAG, "onCreateSuccess: " + sdp.type);
            try {
                JSONObject payload = new JSONObject();
                payload.put("type", sdp.type.canonicalForm());
                payload.put("sdp", sdp.description);
                pc.setLocalDescription(this, sdp);
                synchronized (mPeerListeners) {
                    for (PeerListener peerListener : mPeerListeners) {
                        peerListener.onOfferAnswerCreated(id, sdp.type.canonicalForm(), payload);
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onSetSuccess() {
            Log.e(TAG, "onSetSuccess ");
            if (isInitiator) {
                // For offering peer connection we first create offer and set
                // local SDP, then after receiving answer set remote SDP.
                if (pc.getRemoteDescription() == null) {
                    // We've just set our local SDP so time to send it.
                    Log.d(TAG, "Local SDP set succesfully isInitiator = " + isInitiator);
//                    events.onLocalDescription(localSdp);
                } else {
                    // We've just set remote description, so drain remote
                    // and send local ICE candidates.
                    Log.d(TAG, "Remote SDP set succesfully isInitiator = " + isInitiator);
                    drainCandidates();
                }
            } else {
                // For answering peer connection we set remote SDP and then
                // create answer and set local SDP.
                if (pc.getLocalDescription() != null) {
                    // We've just set our local SDP so time to send it, drain
                    // remote and send local ICE candidates.
                    Log.d(TAG, "Local SDP set succesfully isInitiator = " +isInitiator);
//                    events.onLocalDescription(localSdp);
                    drainCandidates();
                } else {
                    // We've just set remote SDP - do nothing for now -
                    // answer will be created soon.
                    Log.d(TAG, "Remote SDP set succesfully isInitiator = " + isInitiator);
                }
            }
        }

        private void drainCandidates() {
            synchronized (this) {
                if (queuedIceCandiadate != null) {
                    Log.d(TAG, "Add " + queuedIceCandiadate.size() + " remote candidates");
                    for (IceCandidate candidate : queuedIceCandiadate) {
                        pc.addIceCandidate(candidate);
                    }
                    queuedIceCandiadate = null;
                }
            }
        }

        @Override
        public void onCreateFailure(String error) {
            Log.e(TAG, "onCreateFailure: " + error);
        }

        @Override
        public void onSetFailure(String error) {
            Log.e(TAG, "onSetFailure: " + error);
        }
    }

    public interface PeerListener {
        public void onOfferAnswerCreated(String id, String type, JSONObject payload);

        public void onIceCandidate(String id, String type, JSONObject payload);

        public void onIceDisconnect(String id);

        public void onIceConnect(String id);

        public void onCompleted(String id);

        public void onAddRemoteStream(String id, MediaStream mediaStream);

        public void onMessage(String id, byte buffer[]);
    }
}
